#include "Curve.h"
#include "Cube.h"

// helper methods
namespace Curves
{
	QVector<MyVertex> deBoreC2ToBezierC2(QVector<MyVertex> & deBorePoints)
	{
		QVector3D color(1,0,1);

		QVector<MyVertex> result;
		if(deBorePoints.count() < 4)
			return result;
		result.reserve((deBorePoints.count()-3) * 3 +1);
		int n = deBorePoints.count() - 3;
		for(int i=0; i < n; ++i)
		{
			QVector3D bSegment[4];
			QVector3D tmp = (deBorePoints[i+2].position - deBorePoints[i+1].position)/3.0f;
			bSegment[0] = deBorePoints[i+1].position + (deBorePoints[i].position - deBorePoints[i+1].position)/3.0f;
			bSegment[1] = deBorePoints[i+1].position + tmp;
			bSegment[2] = bSegment[1] + tmp;
			bSegment[3] = deBorePoints[i+2].position + (deBorePoints[i+3].position - deBorePoints[i+2].position)/3.0f;
			bSegment[0] = bSegment[0] + (bSegment[1] - bSegment[0]) * 0.5f;
			bSegment[3] = bSegment[2] + (bSegment[3] - bSegment[2]) * 0.5f;
			if(i == 0)
				result.append(MyVertex(bSegment[0], color));
			result.append(MyVertex(bSegment[1], color));
			result.append(MyVertex(bSegment[2], color));
			result.append(MyVertex(bSegment[3], color));
		}

		return result;
	}

	QVector<MyVertex> bezierC2ToDeboreC2(QVector<MyVertex> & bezierPoints)
	{
		QVector3D color(1,1,0);

		QVector<MyVertex> result;
		if(bezierPoints.count() < 4)
			return result;
		result.reserve((bezierPoints.count() - 1)/3);

		QVector<MyVertex> segment = bezierPoints.mid(0,4);
		QVector3D begin = segment[0].position;
		QVector3D tmp = begin + begin - segment[1].position; // przedluzenie pierwszej czesci segmentu
		begin = segment[1].position + segment[1].position - segment[2].position; // lewe przedluzenie srodkowej czesci, drugi punkt deBore
		tmp = tmp + 2*(tmp - begin); // pierwszy punkt deBore
		
		result.push_back(MyVertex(tmp,color)); // d1
		result.push_back(MyVertex(begin, color)); // d2
		
		begin = segment[2].position + segment[2].position - segment[1].position; // prawe przedluzenie srodkowej czesci, 3 punkt deBore
		
		result.push_back(MyVertex(begin, color));

		tmp = segment[3].position;
		tmp = tmp + tmp - segment[2].position; // przedluzenie trzeciej czesci segmentu
		tmp = tmp + 2*(tmp - begin); // czwarty punkt deBore
		
		result.push_back(MyVertex(tmp, color));

		for(int i=6; i<bezierPoints.count(); i += 3)
		{
			segment = bezierPoints.mid(i-3,4);		
			begin = segment[2].position + segment[2].position - segment[1].position; // prawe przedluzenie srodkowej czesci, 3 punkt deBore
		
			tmp = segment[3].position;
			tmp = tmp + tmp - segment[2].position; // przedluzenie trzeciej czesci segmentu
			tmp = tmp + 2*(tmp - begin); // czwarty punkt deBore
		
			result.push_back(MyVertex(tmp, color));		
		}
		return result;
	}
// end helper methods
	// now Curve definition
	Curve::Curve(void): Primitive(1, 20), VerticesMax(1000 * 3)
	{
		bezierPoints.reserve(1000);
		build();
		deBorePointsColor = QVector3D(1.0, 0,0 );
		bezierPointsColor = QVector3D(1, 1, 0);
		curveColor = QVector3D(0, 1, 1);
		selectedDeBorePointsColor = QVector3D(0, 1, 0);
		selectedBezierPointsColor = QVector3D(0, 0, 1);
	}


	Curve::~Curve(void)
	{
	}

	void Curve::build()
	{
		indicesArray.reserve(VerticesMax);
		for(int i=0; i< VerticesMax; ++i)
		{
			indicesArray.insert(indicesArray.end(), 3, i);
		}
		indices = VerticesMax;
	}

	QVector<int> Curve::getCloseDeBorePointsInRange(QVector3D point, float range)
	{
		QVector<int> result;
		float minDist = 100.0f;

		for(int i=0; i< deBorePoints.count(); ++i)
		{
			
			float dist = (deBorePoints[i].position - point).length();
			if(dist <= range)
			{
				result.append(i);
			}
		}
		return result;
	}

	void Curve::deselectDeBorePoint(int i)
	{
		if(i < 0 || 
			i >= deBorePoints.count() )
		{
			return;
		}

		deBorePoints[i].color = deBorePointsColor;
	}

	void Curve::selectDeBorePoint(int i)
	{
		if(i < 0 || i >= deBorePoints.count())
		{
			return;
		}
		MyVertex& selected = deBorePoints[i];
		// selection
		selected.color = selectedDeBorePointsColor;
	}

	void Curve::moveDeBoreTo(int i, QVector3D position)
	{
		if(i < 0 || 
			i >= deBorePoints.count() )
		{
			return;
		}
		deBorePoints[i].position = position;
		// TODO napisac to optymalniej :)
		bezierPoints = deBoreC2ToBezierC2(deBorePoints);
		refresGpuData(bezierPoints);
	}

	void Curve::DrawDeBore(QGLShaderProgram& shaderProgram, QMatrix4x4& viewMatrix, QMatrix4x4& projectionMatrix)
	{
		MyVertex v;
		foreach (v, deBorePoints)
		{
			Cube c(v.position, 0.02f);
			c.setColor(v.color);
			c.DrawShader(shaderProgram, viewMatrix, projectionMatrix);
		}
	}

	void Curve::DrawShader(QGLShaderProgram& shaderProgram, QMatrix4x4& viewMatrix, QMatrix4x4& projectionMatrix)
	{
		if(isInited == false)
			initBuffers(QGLBuffer::DynamicCopy);
		mVertexVBO.bind();
		mIndexVBO.bind();
	
		QMatrix4x4 modelMatrix;
		modelMatrix.translate(translation);
		modelMatrix.scale(sizeScale);

		shaderProgram.bind();

		int inPositionLocation[4];
		inPositionLocation[0] = shaderProgram.attributeLocation("inPosition0");
		inPositionLocation[1] = shaderProgram.attributeLocation("inPosition1");
		inPositionLocation[2] = shaderProgram.attributeLocation("inPosition2");
		inPositionLocation[3] = shaderProgram.attributeLocation("inPosition3");
		int inColorLocation = shaderProgram.attributeLocation("inColor");

		int inModelMatrixLocation = shaderProgram.uniformLocation("inModelMatrix");
		int inViewMatrixLocation = shaderProgram.uniformLocation("inViewMatrix");
		int inProjectionMatrixLocation = shaderProgram.uniformLocation("inProjectionMatrix");
		int inVerticesLocation = shaderProgram.uniformLocation("inVertices");

		for(int i = 0; i<4; ++i)
		{
			shaderProgram.enableAttributeArray(inPositionLocation[i]);
			shaderProgram.setAttributeBuffer(inPositionLocation[i], GL_FLOAT, i * sizeof(MyVertex), 3, sizeof(MyVertex));
		}

		shaderProgram.enableAttributeArray(inColorLocation);
		shaderProgram.setAttributeBuffer(inColorLocation, GL_FLOAT, 12, 3, sizeof(MyVertex));
		shaderProgram.setUniformValue(inModelMatrixLocation, modelMatrix);
		shaderProgram.setUniformValue(inViewMatrixLocation, viewMatrix);
		shaderProgram.setUniformValue(inProjectionMatrixLocation, projectionMatrix);
	
		shaderProgram.setUniformValue(inVerticesLocation, deBorePoints.count());

		//glDrawElements(GL_POINTS, 10, GL_UNSIGNED_INT, 0);
		glDrawElements(GL_POINTS, (deBorePoints.count()-3)*3, GL_UNSIGNED_INT, 0);

		for(int i = 0; i<4; ++i)
		{
			shaderProgram.disableAttributeArray(inPositionLocation[i]);
		}
		shaderProgram.disableAttributeArray(inColorLocation);
	
		shaderProgram.release();
	
		mVertexVBO.release();
		mIndexVBO.release();
	}
	
	void Curve::refresGpuData(QVector<MyVertex> & points)
	{
		mVertexVBO.bind();
		mVertexVBO.allocate(deBorePoints.data(), sizeof(MyVertex)*deBorePoints.count());
		mVertexVBO.release();
	}
}